package com.ubxtech.core.utils;

/**
 *
 * https://blog.csdn.net/lero_online/article/details/103101831?utm_medium=distribute.pc_relevant.none-task-blog-2~default~baidujs_baidulandingword~default-1-103101831-blog-122670044.pc_relevant_3mothn_strategy_and_data_recovery&spm=1001.2101.3001.4242.2&utm_relevant_index=4
 * @author Victor.Xiao
 * @since 2022-10-28 17:33
 **/
public class CRC16ModbusLocal {
    /**
     * 正序校验,倍加信是用此种方法
     * @author Victor.Xiao
     * @since 2022-10-28 19:34
     * @param dataHexStr dataHexStr
     * @return java.lang.String
    */
    public static String crc16Left(String dataHexStr)
    {
        //字符串类型的16进制字符串转换为字节数组
        byte[] data = hex2Bytes(dataHexStr);
        //初始化寄存器 这个转为short类型 是因为java中 short类占16个字节
        short crci= (short) 0xffff;
        int i,j;
        //循环处理字节数组中的每一个字节
        for ( j = 0; j < data.length; j++)
        {
            //字节逆序（一个字节占8位 我们从寄存器的最高位处理数据 所以数据左移8）写入寄存器需要与原值进行一次异或
            crci ^= (reverse8BITS(data[j])<<8);
            for ( i = 0; i < 8; i++){
                //判断即将左移 出的是不是1，如果是1则与多项式进行异或。（此处个人认为和小学学的除法算法中在结果上写1或者0类似）
                if ((crci & 0x8000) == 0x8000){
                    crci <<= 1;
                    //将数据与约定的多项式做（模二除/异或）
                    crci ^= 0x8005;
                }else{
                    crci <<= 1;
                }
            }
        }
        //结果值按位反转（结果值是一定小于多项式的16位，所以按位反转跟8位的方法不同） 然后与结果异或值异或
        String res  = Integer.toHexString(reverse16BITS(crci)^0x0000);
        return res;
    }

    /**
     * 正序校验,然后反转结果,低字节在前,高字节在后
     * @author Victor.Xiao
     * @since 2022-10-28 19:34
     * @param dataHexStr dataHexStr
     * @return java.lang.String
     */
    public static String crc16LeftRevert(String dataHexStr)
    {
        String hex = crc16Left(dataHexStr);
        return hex.substring(2) + hex.substring(0, 2);
    }


    //将16进制字符串转换为byte[]
    public static byte[] hex2Bytes(String str) {
        if(str == null || str.trim().equals("")) {
            return new byte[0];
        }
        byte[] bytes = new byte[str.length() / 2];
        for(int i = 0; i < str.length() / 2; i++) {
            String subStr = str.substring(i * 2, i * 2 + 2);
            bytes[i] = (byte) Integer.parseInt(subStr, 16);
        }
        return bytes;
    }

    //8位逆序反转 （逆序反转的方式有很多，之前看的帖子说这样运算最快，未实际验证）
    public static int reverse8BITS(int b){
        //交换1位
        b =(b & 0xaa) >> 1 | (b & 0x55) << 1;
        //交换2位
        b =(b & 0xcc) >> 2 | (b & 0x33) << 2;
        //交换4位
        b =(b & 0xf0) >> 4 | (b & 0x0f) << 4;
        return b;
    }

    //16位逆序反转
    private static int reverse16BITS(int b){
        //交换1位
        b =(b & 0xaaaa) >> 1 | (b & 0x5555) << 1;
        //交换2位
        b =(b & 0xcccc) >> 2 | (b & 0x3333) << 2;
        //交换4位
        b =(b & 0xf0f0) >> 4 | (b & 0x0f0f) << 4;
        //交换8位
        b =(b & 0xff00) >> 8 | (b & 0x00ff) << 8;
        return b;
    }

    /**
     * 逆序校验的方式，与CRC的标准要求算法不同
     * @author Victor.Xiao
     * @since 2022-10-28 19:34
     * @param dataHexStr dataHexStr
     * @return java.lang.String
    */
    public static String crc16Right(String dataHexStr)
    {
        dataHexStr = dataHexStr.replaceAll(" ", "");
        //字符串类型的16进制字符串转换为字节数组
        byte[] data = dataHexStr.getBytes();
        //初始化寄存器 我们用数据的低八位进行处理,已经可以不用限制寄存器的个数了
        //我测试了int long 都能达到预期的效果 但是short 反而数据错误 没想明白为什么。
        int crci = 0xffff;
        int i,j;
        for ( j = 0; j < data.length; j++)
        {
            //此处&0x00ff的目的是为了将数据放入寄存器的低8位并与寄存器异或
            crci ^=  data[j] & 0x00ff;
            for ( i = 0; i < 8; i++){
                //判断即将右移的是不是1 如果是1则与多项式进行异或
                if ((crci & 0X0001) == 0X0001){
                    crci >>= 1;
                    //将数据与约定的多项式做（模二除/异或）  数据流向变了此处的异或值实际是8005的逆序反转a001
                    crci ^= 0xa001;
                }else{
                    crci >>= 1;
                }
            }
        }
        //结果值与0x0000异或 其实值不会改变，不异或对结果没有影响
        String res  = Integer.toHexString( crci^ 0x0000) ;
        return res;
    }

    public static void main(String[] args) {
        System.out.println(CRC16ModbusLocal.crc16Left("000F00001211000001FFFF0000"));
        System.out.println(CRC16ModbusLocal.crc16LeftRevert("FF0300090005"));
    }

}
